﻿/*******************************************************************************
 * You may amend and distribute as you like, but don't remove this header!
 *
 * PblReader provides the ability to read PowerBuilder PBL files to extract source code.
 * See http://pblreader.codeplex.com/ for details.
 *
 * Copyright (C) 2012  Wagner DosAnjos
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU Lesser General Public License for more details.
 *
 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
 *
 * All code and executables are provided "as is" with no warranty either express or implied. 
 * The author accepts no liability for any damage or loss of business that this product may cause. 
 *******************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Pbl.Reader;
using System.Globalization;

namespace PblExport
{
    class Program
    {
        private enum Command
        {
            Help,
            ListShort,
            ListLong,
            Export,
            ExportToConsoleOut
        }

        private static Command? _cmd = null;

        private static List<string> _pbls = new List<string>();
        private static string _outputFolder;
        private static DateTime? _dateFilter;
        private static Regex _nameFilter;
        private static bool _nameFilterExclude;
        private static bool _verbose;

        static void Main(string[] args)
        {
            ProcessArguments(args);

            switch (_cmd)
            {
                case Command.Help:
                    Help();
                    break;

                case Command.ListShort:
                    ListShort();
                    break;

                case Command.ListLong:
                    ListLong();
                    break;

                case Command.Export:
                case Command.ExportToConsoleOut:
                    Export();
                    break;
            }
        }

        private static void Help()
        {
            var help = "PblExport {0} {1}. Usage:\n" +
                       "pbl [-?] [-e:folder] [-l] [-L] [-i:filter] [-x:filter] [-t:yyyymmdd] pblfile list\n" +
                       "  If PBL files are provided, the default action is to a short list of objects.\n" +
                       "  Otherwise this help is displayed.\n" +
                       "  -e   export objects to folder     -i   include only matching objects\n" +
                       "  -l   short object list            -x   eXclude matching objects\n" +
                       "  -L   long object list             -t   only objects updated on/after yyyymmdd\n" +
                       "  -E   export objects to stdio      -?   show this help\n";

            Console.WriteLine(String.Format(help, AssemblyInfoUtil.AssemblyVersion, AssemblyInfoUtil.Description));
        }

        private static void ListShort()
        {
            bool displayPbl = (_pbls.Count > 1);

            foreach (var pblFileName in _pbls.OrderBy(_ => _))
            {
                using (var pbl = new PblReader(pblFileName))
                {
                    foreach (var entry in pbl.Entries.Where(_ => Filter(_)))
                    {
                        if (displayPbl)
                        {
                            Console.WriteLine("{0}#{1}", pblFileName, entry.Name);
                        }
                        else
                        {
                            Console.WriteLine(entry.Name);
                        }
                    }
                }
            }
        }

        private static void ListLong()
        {
            bool displayPbl = (_pbls.Count > 1);

            foreach (var pblFileName in _pbls.OrderBy(_ => _))
            {
                using (var pbl = new PblReader(pblFileName))
                {
                    foreach (var entry in pbl.Entries.Where(_ => Filter(_)))
                    {
                        if (displayPbl)
                        {
                            Console.WriteLine("{0}#{1} ({2:yyyy-MM-dd}) [{3}]", pblFileName, entry.Name, entry.Timestamp, entry.Comment);
                        }
                        else
                        {
                            Console.WriteLine("{0} ({1:yyyy-MM-dd}) [{2}]", entry.Name, entry.Timestamp, entry.Comment);
                        }
                    }
                }
            }
        }

        private static void Export()
        {
            bool createFolderForPbl = (_pbls.Count > 1);

            foreach (var pblFileName in _pbls)
            {
                using (var pbl = new PblReader(pblFileName))
                {
                    var folder = _outputFolder;

                    if (createFolderForPbl)
                    {
                        folder = Path.Combine(folder, pbl.Name);

                        if (!Directory.Exists(folder))
                        {
                            Directory.CreateDirectory(folder);
                        }
                    }

                    foreach (var entry in pbl.Entries.Where(_ => Filter(_)))
                    {
                        if (_cmd == Command.Export)
                        {
                            var filename = Path.Combine(folder, entry.Name);

                            if (_verbose) Console.WriteLine(filename);

                            using (var writer = File.Create(filename))
                            {
                                var content = pbl.GetContentAsByteArray(entry);

                                writer.Write(content, 0, content.Length);
                            }
                        }
                        else
                        {
                            if (_verbose) Console.WriteLine("**********  {0}  **********", entry.Name);

                            Console.WriteLine(pbl.GetContentAsString(entry));
                        }
                    }
                }
            }
        }

        private static void ProcessArguments(string[] args)
        {
            bool hasErrors = false;

            foreach (var arg in args)
            {
                bool invalid = false;

                if (!arg.StartsWith("-"))
                {
                    var pbls = GetPblFileNames(arg);

                    if (pbls == null)
                    {
                        invalid = true;
                    }
                    else
                    {
                        _pbls.AddRange(pbls); // Pbl

                        if (_cmd == null)
                        {
                            _cmd = Command.ListShort;
                        }
                    }
                }
                else if (arg.Length < 2)
                {
                    invalid = true;
                }
                else
                {
                    char option = arg[1];

                    switch (option)
                    {
                        case '?':   // Help
                            _cmd = Command.Help;
                            break;

                        case 'v':   // Verbose
                            _verbose = true;
                            break;

                        case 'l':   // List Short (only object names)
                            _cmd = Command.ListShort;
                            break;

                        case 'L':   // List Long (object names, update date, comment)
                            _cmd = Command.ListLong;
                            break;

                        case 'e':   // Export to specified folder
                            if (_outputFolder != null)
                            {
                                invalid = true;
                            }
                            else
                            {
                                _outputFolder = GetFolder(arg);
                                if (_outputFolder != null)
                                {
                                    _cmd = Command.Export;
                                }
                                else
                                {
                                    invalid = true;
                                }
                            }
                            break;

                        case 'E':
                            _cmd = Command.ExportToConsoleOut;
                            break;

                        case 'i':
                            if (_nameFilter != null)
                            {
                                invalid = true;
                            }
                            else
                            {
                                _nameFilter = GetFilter(arg);

                                invalid = (_nameFilter == null);
                            }
                            break;

                        case 'x':
                            if (_nameFilter != null)
                            {
                                invalid = true;
                            }
                            else
                            {
                                _nameFilterExclude = true;
                                _nameFilter = GetFilter(arg);

                                invalid = (_nameFilter == null);
                            }
                            break;

                        case 't':
                            if (_dateFilter != null)
                            {
                                invalid = true;
                            }
                            else
                            {
                                _dateFilter = GetDate(arg);

                                invalid = (_dateFilter == null);
                            }
                            break;

                        default:
                            break;
                    }
                }

                if (invalid)
                {
                    hasErrors = true;

                    Console.WriteLine("Invalid option '{0}'", arg);
                }
            }

            if (_cmd == null || hasErrors)
            {
                _cmd = Command.Help;
            }
        }

        private static DateTime? GetDate(string arg)
        {
            DateTime? dateFilter = null;

            if (arg.Length == 11)
            {
                DateTime date;

                if (DateTime.TryParseExact(arg.Substring(3), "yyyyMMdd", CultureInfo.CurrentCulture, DateTimeStyles.None, out date))
                {
                    dateFilter = date;
                }
            }

            return dateFilter;
        }

        private static string[] GetPblFileNames(string arg)
        {
            string[] pbls = null;

            if (arg.Contains("*") || arg.Contains("?"))
            {
                try
                {
                    pbls = Directory.GetFiles(Path.GetDirectoryName(arg), Path.GetFileName(arg), SearchOption.TopDirectoryOnly);
                }
                catch (IOException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            else
            {
                if (File.Exists(arg))
                {
                    pbls = new string[] { arg };
                }
                else
                {
                    Console.WriteLine("File '{0}' not found.", arg);
                }
            }

            return pbls;
        }

        private static Regex GetFilter(string arg)
        {
            Regex filter = null;

            if (arg.Length > 3 && arg[2] == ':')
            {
                var pattern = new StringBuilder();

                pattern.Append('^');

                foreach (var f in arg.Substring(3).Split(';'))
                {
                    if (pattern.Length > 1)
                    {
                        pattern.Append("|");
                    }

                    pattern.Append("(")
                           .Append(Regex.Escape(f).Replace(@"\*", ".*").Replace(@"\?", "."))
                           .Append(")");
                }

                pattern.Append('$');

                filter = new Regex(pattern.ToString());
            }

            return filter;
        }

        private static string GetFolder(string arg)
        {
            string folder = null;

            if (arg.Length > 3)
            {
                if (arg[2] == ':')
                {
                    folder = arg.Substring(3);
                }
            }
            else
            {
                folder = ".";
            }

            if (folder != null)
            {
                if (!Directory.Exists(folder))
                {
                    Console.WriteLine("Folder '{0}' not found.", folder);

                    folder = null;
                }
            }

            return folder;
        }

        private static bool Filter(PblEntry entry)
        {
            bool pass = true;

            if (_nameFilter != null)
            {
                var isMatch = _nameFilter.IsMatch(entry.Name);

                pass = (isMatch && !_nameFilterExclude) || (!isMatch && _nameFilterExclude);
            }

            if (pass && _dateFilter != null)
            {
                pass = (entry.Timestamp.CompareTo((DateTime)_dateFilter) > 0);
            }

            return pass;
        }
    }
}
